package com.kartoshka.core 
{
	import com.kartoshka.core.hard.UISprite;
	import com.kartoshka.interfaces.IMeasurable;
	import com.kartoshka.interfaces.IPainter;
	import com.kartoshka.interfaces.IValidatable;
	import com.kartoshka.managers.ValidationManager;
	import com.kartoshka.painters.NullPainter;
	import com.kartoshka.utils.CoreUtil;

	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Rectangle;
	
	use namespace ui;

	/**
	 * @author Alexander Zats, 2009
	 */
	public class UIBase extends UISprite implements IValidatable, IMeasurable
	{

		private const _validationManager : ValidationManager = ValidationManager.instance;
		private const _uid : String = CoreUtil.getUid( this );

		/*
		 * Measurements
		 */
		protected var _height : Number = 0;
		protected var _width : Number = 0;

		protected var _marginLeft : Number;
		protected var _marginTop : Number;
		protected var _marginRight : Number;
		protected var _marginBottom : Number;

		protected var _paddingLeft : Number = 0;
		protected var _paddingTop : Number = 0;
		protected var _paddingRight : Number = 0;
		protected var _paddingBottom : Number = 0;

		protected var _percentWidth : Number;
		protected var _percentHeight : Number;

		private var _layoutWidth : Number = 0;
		private var _layoutHeight : Number = 0;

		protected var _verticalCenter : Number;
		protected var _horizontalCenter : Number;

		protected var _cX : Number;
		protected var _cY : Number;
		protected var _rCX : Number;
		protected var _rCY : Number;

		/*
		 * Layers.
		 */
		ui var layerChrome : Sprite;
		ui var layerContent : Sprite;
		ui var layerOverlay : Sprite;

		/*
		 * Background & layout.
		 */
		private var _background : IPainter;
//		private var _layout : ILayout;
		
		public function UIBase()
		{
			super( );

			_background = new NullPainter( );
//			_layout = NullLayout.instance;
			
			super.addChild( layerChrome = new Sprite( ) );
			super.addChild( layerContent = new Sprite( ) );
			super.addChild( layerOverlay = new Sprite( ) );
			
			this.addEventListener( "marginLeftChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "marginTopChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "marginRightChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "marginBottomChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "paddingLeftChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "paddingTopChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "paddingRightChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "paddingBottomChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "widthChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "heightChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "percentWidthChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "percentHeightChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "horizontalCenterChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "verticalCenterChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "cXChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "cYChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "rCXChanged", layoutChangeHandler, false, 0, true );
			this.addEventListener( "rCYChanged", layoutChangeHandler, false, 0, true );
		}

		private function layoutChangeHandler( event : Event ) : void
		{
			var workingArea : Rectangle;
			if ( ! this.parent )
			{
				workingArea = new Rectangle( 0, 0, 0, 0 );
			}
			else if ( this.parent is IMeasurable )
			{
				var measurableParent : IMeasurable = parent as IMeasurable;
				workingArea = new Rectangle( measurableParent.paddingLeft, measurableParent.paddingTop, measurableParent.layoutWidth - measurableParent.paddingLeft - measurableParent.paddingRight, measurableParent.layoutHeight - measurableParent.paddingTop - measurableParent.paddingBottom );
			}
			else
			{
				workingArea = new Rectangle( 0, 0, parent.width, parent.height );
			}
			
			switch ( event.type )
			{
				case "xChanged":
					_cX = _rCX = _horizontalCenter = _marginLeft = NaN;
					break;
				case "yChanged":
					_cY = _rCY = _verticalCenter = _marginTop = NaN;
					break;
				case "marginLeftChanged":
					if ( ! isNaN( _marginRight ) )
					{
						_width = _layoutWidth = workingArea.width - _marginLeft - _marginRight;
						_percentWidth = NaN;
					}
					this.x = _marginLeft;
					_cX = _rCX = _horizontalCenter = NaN;
					break;
				case "marginTopChanged":
					if ( ! isNaN( _marginBottom ) )
					{
						_height = _layoutHeight = workingArea.height - _marginTop - _marginBottom;
						_percentHeight = NaN;
					}
					this.y = _marginTop;
					_cY = _rCY = _verticalCenter = NaN;
					break;
				case "marginRightChanged":
					if ( ! isNaN( _marginLeft ) )
					{
						_width = _layoutWidth = workingArea.width - _marginLeft - _marginRight;
						_percentWidth = NaN;
					}
					this.x = workingArea.width - _marginRight - _layoutWidth;
					_cX = _rCX = _horizontalCenter = NaN;
					break;
				case "marginBottomChanged":
					if ( ! isNaN( _marginTop ) )
					{
						_height = _layoutHeight = workingArea.height - _marginTop - _marginBottom;
						_percentHeight = NaN;
					}
					this.y = workingArea.height - _marginTop - _marginBottom;
					_cY = _rCY = _verticalCenter = NaN;
					break;
				case "widthChanged":
					if ( ! isNaN( _marginLeft ) && ! isNaN( _marginRight ) ) 
					{
						_marginRight = NaN;
					}
					_layoutWidth = _width;
					_percentWidth = NaN;
					break;
				case "heightChanged":
					if ( ! isNaN( _marginTop ) && ! isNaN( _marginBottom ) ) 
					{
						_marginBottom = NaN;
					}
					_layoutHeight = _height;
					_percentHeight = NaN;
					break;
				case "percentWidthChanged":
					if ( ! isNaN( _marginLeft ) && ! isNaN( _marginRight ) ) 
					{
						_marginRight = NaN;
					}
					_layoutWidth = _width = workingArea.width * _percentWidth * 0.01;
					break;
				case "percentHeightChanged":
					if ( ! isNaN( _marginTop ) && ! isNaN( _marginBottom ) ) 
					{
						_marginBottom = NaN;
					}
					_layoutHeight = _height = workingArea.height * _percentHeight * 0.01;
					break;
				case "verticalCenterChanged":
					_cY = _rCY = _marginTop = _marginBottom = NaN;
					break;
				case "horizontalCenterChanged":
					_cX = _rCX = _marginLeft = _marginRight = NaN;
					break;
				case "cX":
					_horizontalCenter = _marginLeft = _marginRight = NaN;
					if ( isNaN( _rCX ) ) 
					{
						_rCX = 0;
					}
					break;
				case "rCX":
					_horizontalCenter = _marginLeft = _marginRight = NaN;
					if ( isNaN( _cX ) ) 
					{
						_cX = 0;
					}
					break;
				case "cY":
					_verticalCenter = _marginTop = _marginBottom = NaN;
					if ( isNaN( _cY ) ) 
					{
						_cY = 0;
					}
					break;
				case "rCY":
					_verticalCenter = _marginTop = _marginBottom = NaN;
					if ( isNaN( _cY ) ) 
					{
						_cY = 0;
					}
					break;
				case "paddingLeftChanged":
				case "paddingTopChanged":
				case "paddingRightChanged":
				case "paddingBottomChanged":
					// nothing to do
					break;
			}
			
			invalidate( ValidationManager.ALL );
		}

		
		//---------------------------------------------------------------------------------------------------------------
		//	Validation block
		//---------------------------------------------------------------------------------------------------------------

		public function invalidate( flag : int ) : void
		{
			_validationManager.addToQueue( this );
		}

		public function validate( force : Boolean = false ) : void
		{
			validateMeasurements( );
			validateChrome( );
			validateChildren( force );
		}

		protected function validateMeasurements() : void
		{
			var workingArea : Rectangle;
			if ( ! this.parent )
			{
				workingArea = new Rectangle( 0, 0, 0, 0 );
			}
			else if ( this.parent is IMeasurable )
			{
				var measurableParent : IMeasurable = this.parent as IMeasurable;
				workingArea = new Rectangle( measurableParent.paddingLeft, measurableParent.paddingTop, measurableParent.layoutWidth - measurableParent.paddingLeft - measurableParent.paddingRight, measurableParent.layoutHeight - measurableParent.paddingTop - measurableParent.paddingBottom );
			}
			else
			{
				workingArea = new Rectangle( 0, 0, parent.width, parent.height );
			}

			//Width in percent
			if ( ! isNaN( _percentWidth ) )
			{
				_layoutWidth = _width = workingArea.width * 0.01 * _percentWidth;
			}
			else if ( ! isNaN( _width ) )
			{
				_layoutWidth = _width;
			}

			//Height in percent
			if ( ! isNaN( _percentHeight ) )
			{
				_layoutHeight = _height = workingArea.height * 0.01 * _percentHeight;
			}
			else if ( ! isNaN( _height ) )
			{
				_layoutHeight = _height;
			}
			
			//Things, which involves positioning.
			
			//Horizontal margins.
			if ( ! isNaN( _marginLeft ) )
			{
				if ( ! isNaN( _marginRight ) )
				{
					_layoutWidth = _width = workingArea.width - _marginLeft - _marginRight;
				}
				this.x = workingArea.left + _marginLeft;
			}
			else if ( ! isNaN( _marginRight ) )
			{
				this.x = workingArea.right - _layoutWidth - _marginRight;
			}
			//Horizontal center.
			else if ( ! isNaN( _horizontalCenter ) )
			{
				this.x = workingArea.left + ( workingArea.width - _layoutWidth ) / 2 + _horizontalCenter;
			}
			//cX & rCX
			else if ( ! isNaN( _cX ) )
			{
				this.x = workingArea.left + workingArea.width * _cX - _layoutWidth * _rCX;
			}
			//Default case.
			else
			{
				this.x = workingArea.left;
			}
			
			//Vertical margins.
			if ( ! isNaN( _marginTop ) )
			{
				if ( ! isNaN( _marginBottom ) )
				{
					_layoutHeight = _height = workingArea.height - _marginTop - _marginBottom;
				}
				this.y = workingArea.top + _marginTop;
			}
			else if ( ! isNaN( _marginBottom ) )
			{
				this.y = workingArea.bottom - _layoutHeight - _marginBottom;
			}
			//Vertical centers.
			else if ( ! isNaN( _verticalCenter ) )
			{
				this.y = workingArea.top + ( workingArea.height - _layoutHeight ) / 2 + _verticalCenter;
			}
			//cY & rCY
			else if ( ! isNaN( _cY ) )
			{
				this.y = workingArea.top + workingArea.height * _cY - _layoutHeight * _rCY;
			}
			//Default case.
			else
			{
				this.y = workingArea.top;
			}
		}

		protected function validateChrome() : void
		{
			layerChrome.graphics.clear( );
			_background.draw( layerChrome.graphics, _layoutWidth, _layoutHeight );
		}

		protected function validateChildren( force : Boolean = false ) : void
		{
			var child : IValidatable;
			for ( var i : int = 0; i < this.numChildren ; i++ )
			{
				child = this.getChildAt( i ) as IValidatable; 
				if ( ! child ) continue;
				//TODO child.invalidate should have correct flag, probably ALL isn't suitable
				force ? child.validate( true ) : child.invalidate( ValidationManager.ALL );
			}
		}

		
		//---------------------------------------------------------------------------------------------------------------
		//	Measurements
		//---------------------------------------------------------------------------------------------------------------

		public function setMargins( left : Number = NaN, top : Number = NaN, right : Number = NaN, bottom : Number = NaN ) : void
		{
			if ( ! isNaN( left ) && left != _marginLeft )
			{
				_marginLeft = left;
				this.dispatchEvent( new Event( "marginLeftChanged" ) );
			}
			if ( ! isNaN( top ) && top != _marginTop ) 
			{
				_marginTop = top;
				this.dispatchEvent( new Event( "marginTopChanged" ) );
			}
			if ( ! isNaN( right ) && right != _marginRight ) 
			{
				_marginRight = right;
				this.dispatchEvent( new Event( "marginRightChanged" ) );
			}
			if ( ! isNaN( bottom ) && bottom != _marginBottom  )
			{
				_marginBottom = bottom;
				this.dispatchEvent( new Event( "marginBottomChanged" ) );
			}
		}

		public function getMargins() : Rectangle
		{
			return new Rectangle( _marginLeft, _marginTop, _marginRight - _marginLeft, _marginBottom - _marginTop );
		}

		public function set marginLeft( value : Number ) : void
		{
			if ( value == _marginLeft ) return;
			_marginLeft = value;
			this.dispatchEvent( new Event( "marginLeftChanged" ) );
		}

		public function get marginLeft() : Number
		{
			return _marginLeft;
		}

		public function set marginTop( value : Number ) : void
		{
			if ( value == _marginTop ) return;
			_marginTop = value;
			this.dispatchEvent( new Event( "marginTopChanged" ) );
		}

		public function get marginTop() : Number
		{
			return _marginTop;
		}

		public function set marginRight( value : Number ) : void
		{
			if ( value == _marginRight ) return;
			_marginRight = value;
			this.dispatchEvent( new Event( "marginRightChanged" ) );
		}

		public function get marginRight() : Number
		{
			return _marginRight;
		}

		public function set marginBottom( value : Number ) : void
		{
			if ( value == _marginBottom ) return;
			_marginBottom = value;
			this.dispatchEvent( new Event( "marginBottomChanged" ) );
		}

		public function get marginBottom() : Number
		{
			return _marginBottom;
		}

		public function setPaddings( left : Number = NaN, top : Number = NaN, right : Number = NaN, bottom : Number = NaN ) : void
		{
			if ( ! isNaN( left ) && left != _paddingLeft )
			{
				_paddingLeft = left;
				this.dispatchEvent( new Event( "paddingLeftChanged" ) );
			}
			if ( ! isNaN( top ) && top != _paddingRight )
			{
				_paddingTop = top;
				this.dispatchEvent( new Event( "paddingRightChanged" ) );
			}
			if ( ! isNaN( right ) && right != _paddingRight ) 
			{
				_paddingRight = right;
				this.dispatchEvent( new Event( "paddingRightChanged" ) );
			}
			if ( ! isNaN( bottom ) && bottom != _paddingBottom ) 
			{
				_paddingBottom = bottom;
				this.dispatchEvent( new Event( "paddingBottomChanged" ) );
			}
		}

		public function getPaddings() : Rectangle
		{
			return new Rectangle( _paddingLeft, _paddingTop, _paddingRight - _paddingLeft, _paddingBottom - _paddingTop );
		}

		public function set paddingLeft( value : Number ) : void
		{
			if ( value == _paddingLeft ) return;
			_paddingLeft = value;
			this.dispatchEvent( new Event( "paddingLeftChanged" ) );
		}

		public function get paddingLeft() : Number
		{
			return _paddingLeft;
		}

		public function set paddingTop( value : Number ) : void
		{
			if ( value == _paddingTop ) return;
			_paddingTop = value;
			this.dispatchEvent( new Event( "paddingTopChanged" ) );
		}

		public function get paddingTop() : Number
		{
			return _paddingTop;
		}

		public function set paddingRight( value : Number ) : void
		{
			if ( value == _paddingRight ) return;
			_paddingRight = value;
			this.dispatchEvent( new Event( "paddingRightChanged" ) );
		}

		public function get paddingRight() : Number
		{
			return _paddingRight;
		}

		public function set paddingBottom( value : Number ) : void
		{
			if ( value == _paddingBottom ) return;
			_paddingBottom = value;
			this.dispatchEvent( new Event( "paddingBottomChanged" ) );
		}

		public function get paddingBottom() : Number
		{
			return _paddingBottom;
		}

		public function setSize( width : Number = NaN, height : Number = NaN ) : void
		{
			if ( ! isNaN( width ) && width != _width )
			{
				_width = width;
				this.dispatchEvent( new Event( "widthChanged" ) );
			}
			if ( ! isNaN( height ) && height != _height )
			{
				_height = height;
				this.dispatchEvent( new Event( "heightChanged" ) );
			}
		}

		override public function set height( value : Number ) : void
		{
			if ( value == _height ) return;
			_height = value;
			this.dispatchEvent( new Event( "heightChanged" ) );
		}

		override public function get height() : Number
		{
			return _height;
		}

		override public function set width( value : Number ) : void
		{
			if ( value == _width ) return;
			_width = value;
			this.dispatchEvent( new Event( "widthChanged" ) );
		}

		override public function get width() : Number
		{
			return _width;
		}

		public function setPercentSize( width : Number = NaN, height : Number = NaN ) : void
		{
			if ( ! isNaN( width ) && width != _percentWidth )
			{
				_percentWidth = width;
				this.dispatchEvent( new Event( "percentWidthChanged" ) );
			}
			if ( ! isNaN( height ) && height != _percentHeight )
			{
				_percentHeight = height;
				this.dispatchEvent( new Event( "percentHeightChanged" ) );
			}
		}

		public function set percentWidth( value : Number ) : void
		{
			if ( value == _percentWidth ) return;
			_percentWidth = value;
			this.dispatchEvent( new Event( "percentWidthChanged" ) );
		}

		public function get percentWidth() : Number
		{
			return _percentWidth;
		}

		public function set percentHeight( value : Number ) : void
		{
			if ( value == _percentHeight ) return;
			_percentHeight = value;
			this.dispatchEvent( new Event( "percentHeightChanged" ) );
		}

		public function get percentHeight() : Number
		{
			return _percentHeight;
		}

		public function set layoutWidth(value : Number) : void
		{
			_layoutWidth = value;
		}

		public function get layoutWidth() : Number
		{
			return _layoutWidth;
		}

		public function set layoutHeight(value : Number) : void
		{
			_layoutHeight = value;
		}

		public function get layoutHeight() : Number
		{
			return _layoutHeight;
		}

		public function setCenters( horizontal : Number = NaN, vertical : Number = NaN ) : void
		{
			if ( ! isNaN( horizontal ) && horizontal != _horizontalCenter )
			{
				_horizontalCenter = horizontal;
				this.dispatchEvent( new Event( "horizontalCenterChanged" ) );
			}
			if ( ! isNaN( vertical ) && vertical != _verticalCenter )
			{
				_verticalCenter = vertical;
				this.dispatchEvent( new Event( "verticalCenterChanged" ) );
			}
		}

		public function set horizontalCenter( value : Number ) : void
		{
			if ( value == _horizontalCenter ) return;
			_horizontalCenter = value;
			this.dispatchEvent( new Event( "horizontalCenterChanged" ) );
		}

		public function get horizontalCenter() : Number
		{
			return _horizontalCenter;
		}

		public function set verticalCenter( value : Number ) : void
		{
			if ( value == _verticalCenter ) return;
			_verticalCenter = value;
			this.dispatchEvent( new Event( "verticalCenterChanged" ) );
		}

		public function get verticalCenter() : Number
		{
			return _verticalCenter;
		}

		public function setCRC( x : Number = NaN, y : Number = NaN, rX : Number = NaN, rY : Number = NaN ) : void
		{
			if ( ! isNaN( x ) && x != _cX ) 
			{
				_cX = x;
				this.dispatchEvent( new Event( "cXChanged" ) );
			}
			if ( ! isNaN( y ) && y != _cY )
			{
				_cY = y;
				this.dispatchEvent( new Event( "cYChanged" ) );
			}
			if ( ! isNaN( rX ) && rX != _rCX ) 
			{
				_rCX = rX;
				this.dispatchEvent( new Event( "rCXChanged" ) );
			}
			if ( ! isNaN( rY ) && rY != _rCY )
			{
				_rCY = rY;
				this.dispatchEvent( new Event( "rCYChanged" ) );
			}
		}

		public function set cX( value : Number ) : void
		{
			if ( value == _cX ) return;
			_cX = value;
			this.dispatchEvent( new Event( "cXChanged" ) );
		}

		public function get cX() : Number
		{
			return _cX;
		}

		public function set cY( value : Number ) : void
		{
			if ( value == _cY ) return;
			_cY = value;
			this.dispatchEvent( new Event( "cYChanged" ) );
		}

		public function get cY() : Number
		{
			return _cY;
		}

		public function set rCX( value : Number ) : void
		{
			if ( value == _rCX ) return;
			_rCX = value;
			this.dispatchEvent( new Event( "rCXChanged" ) );
		}

		public function get rCX() : Number
		{
			return _rCX;
		}

		public function set rCY( value : Number ) : void
		{
			if ( value == _rCY ) return;
			_rCY = value;
			this.dispatchEvent( new Event( "rCYChanged" ) );
		}

		public function get rCY() : Number
		{
			return _rCY;
		}

		override public function set x( value : Number ) : void
		{
			if ( value == super.x ) return;
			super.x = value;
			this.dispatchEvent( new Event( "xChanged" ) );
		}

		override public function get x() : Number
		{
			return super.x;
		}

		override public function set y( value : Number ) : void
		{
			if ( value == super.y ) return;
			super.y = value;
			this.dispatchEvent( new Event( "yChanged" ) );
		}

		override public function get y() : Number
		{
			return super.y;
		}

		public function set background( value : IPainter ) : void
		{
			_background = value;
		}

		public function get background() : IPainter
		{
			return _background;
		}

		public function get uid() : String
		{
			return _uid;
		}

		override public function toString() : String
		{
			return _uid;
		}

		
		//---------------------------------------------------------------------------------------------------------------
		//	Children management block
		//---------------------------------------------------------------------------------------------------------------

		override public function addChild(child : DisplayObject) : DisplayObject
		{
			return layerContent.addChild( child );
		}

		override public function addChildAt(child : DisplayObject, index : int) : DisplayObject
		{
			return layerContent.addChildAt( child, index );
		}

		override public function contains(child : DisplayObject) : Boolean
		{
			return layerContent.contains( child );
		}

		override public function getChildAt(index : int) : DisplayObject
		{
			return layerContent.getChildAt( index );
		}

		override public function getChildByName(name : String) : DisplayObject
		{
			return layerContent.getChildByName( name );
		}

		override public function getChildIndex(child : DisplayObject) : int
		{
			return layerContent.getChildIndex( child );
		}

		override public function get numChildren() : int
		{
			return layerContent.numChildren;
		}

		override public function removeChild(child : DisplayObject) : DisplayObject
		{
			return layerContent.removeChild( child );
		}

		override public function removeChildAt(index : int) : DisplayObject
		{
			return layerChrome.removeChildAt( index );
		}

		override public function setChildIndex(child : DisplayObject, index : int) : void
		{
			layerContent.setChildIndex( child, index );
		}

		override public function swapChildren(child1 : DisplayObject, child2 : DisplayObject) : void
		{
			layerContent.swapChildren( child1, child2 );
		}

		override public function swapChildrenAt(index1 : int, index2 : int) : void
		{
			layerContent.swapChildrenAt( index1, index2 );
		}
	}
}